/* 
 * PB8515 onboard DS1302 RTC test with lcd
 * 
 * 22.06.2011
 * Sami Kukkonen (c)
 *
*/

/* TODO
 * - Support for time zone changing
 * - Support for daylight saving times for predefined countries
 * - Button based UI with debouncing
 * - Programmable alarm
*/

/* FIX
 * - lcd library too big to fit in
*/
 
#include "./board/pb8515tiny.h"
#include "global.h"

#include <avr/io.h>
#include <avr/pgmspace.h>

#include "rprintf.h"

#include "lcd.h"

#include "uart.h"
#include "timer.h"
#include "vt100.h"
#include "cmdline.h"

#include "ds1302light.h"

// Globals
uint8_t RTC_CENTURY = 20; // define this

// RTC table
uint8_t RTC_TABLE[] = {
	0, // Seconds
	0, // Minutes
	0, // Hours
	7, // Day of the week
	1, // Day
	1, // Month
	0, // Two least significant digits of year eg: 20xx
	
	0, // WP bit, 1 = true, 0 = false
	0  // a/p in 12h mode, 0 in 24h mode
};

prog_char MonthDayTable[] PROGMEM = {31,28,31,30,31,30,31,31,30,31,30,31};

/*
prog_char monday_str[] PROGMEM = "ma";
prog_char tuesday_str[] PROGMEM = "ti";
prog_char wednesday_str[] PROGMEM = "ke";
prog_char thursday_str[] PROGMEM = "to";
prog_char friday_str[] PROGMEM = "pe";
prog_char saturday_str[] PROGMEM = "la";
prog_char sunday_str[] PROGMEM = "su";

PROGMEM const char *weekdays_str_p[] = {   
	sunday_str,
	monday_str,
	tuesday_str,
	wednesday_str,
	thursday_str,
	friday_str,
	saturday_str
};

prog_char january_str[] PROGMEM = "tammi";
prog_char february_str[] PROGMEM = "helmi";
prog_char march_str[] PROGMEM = "maalis";
prog_char april_str[] PROGMEM = "huhti";
prog_char may_str[] PROGMEM = "touko";
prog_char june_str[] PROGMEM = "kesä";
prog_char july_str[] PROGMEM = "heinä";
prog_char august_str[] PROGMEM = "elo";
prog_char september_str[] PROGMEM = "syys";
prog_char october_str[] PROGMEM = "loka";
prog_char november_str[] PROGMEM = "marras";
prog_char december_str[] PROGMEM = "joulu";

PROGMEM const char *months_str_p[] = {   
	january_str,
	february_str,
	march_str,
	april_str,
	may_str,
	june_str,
	july_str,
	august_str,
	september_str,
	october_str,
	november_str,
	december_str
};
*/

// Initialize hardware drivers
void HWInit(void);
// Clock data fetching and drawing function
void drawClock(void);

void cliHelp(void);
void cliSetTime(void);
void cliGetTime(void);

uint8_t isLeapYear(uint16_t);
int dayOfWeek(int, int, int);

int main(void) {
	HWInit();
	uint8_t c;
	for (;;) {
		// Let the cmdline handle received uart bytes
		while(uartReceiveByte(&c)) cmdlineInputFunc(c);
		cmdlineMainLoop();
		drawClock();
	}
}

void HWInit(void) {
	// Initialize hardware
	pbInit();
	ds1302Init();
	lcdInit();
	uartInit();
	
	//timer0Init();
	
	// Print constants to LCD
	
	rprintfInit(lcdDataWrite);
	lcdClear();
	lcdGotoXY(0, 0);
	rprintfProgStr(PSTR("Time:"));
	lcdGotoXY(0, 1);
	
	rprintfProgStr(PSTR("Date:"));
	
	rprintfInit(uartSendByte);
	
	// Initialize serial terminal and cli
	vt100Init();
	vt100ClearScreen();
	vt100SetCursorPos(1,0);
	rprintfProgStr(PSTR("\r\nDS1302 real-time clock library test program\r\n"));
	//rprintfProgStr(PSTR("Hello\r\n"));
	cmdlineInit();
	cmdlineSetOutputFunc(uartSendByte);
	
	// add commands to the command database
	cmdlineAddCommand("help",	cliHelp);
	cmdlineAddCommand("set",	cliSetTime);
	cmdlineAddCommand("get",	cliGetTime);
	cmdlineInputFunc('\r');
	
	//timer0SetPrescaler(TIMER_CLK_DIV256);
	//timerAttach(TIMER0OVERFLOW_INT,  drawClock);
}

void cliHelp(void)
{
	rprintfCRLF();

	rprintfProgStr(PSTR("Commands:\r\n"));
	rprintfProgStr(PSTR("set - set time; format: yyyy mm dd hh mm ss\r\n"));
	rprintfProgStr(PSTR("get - returns current time in ISO 8601 format\r\n"));
	
	rprintfCRLF();
}

void cliSetTime(void)
{
	rprintfCRLF();
	
	rprintfProgStr(PSTR("Setting time\r\n"));
	
	uint8_t error = 0;
	uint16_t	year 	= cmdlineGetArgInt(1);
	uint8_t		month 	= cmdlineGetArgInt(2);
	uint8_t		day 	= cmdlineGetArgInt(3);
	uint8_t		hours	= cmdlineGetArgInt(4);
	uint8_t		minutes	= cmdlineGetArgInt(5);
	uint8_t		seconds	= cmdlineGetArgInt(6);

	if (year >= 2000 && year <= 2099) {
		if (month >= 1 && month <= 12) {
			uint8_t daysInMonth = pgm_read_byte(&MonthDayTable[month-1]);
			if (isLeapYear(year) == 1 && month == 2) { // Check if it is leap year and february and add one day if it is
				daysInMonth++;
			} 
			if (day >= 1 && day <= daysInMonth) {
				if (hours >= 0 && hours <= 23) {
					if (minutes >= 0 && minutes <= 59) {
						if (seconds >= 0 && seconds <= 59) {
							RTC_TABLE[SECONDS]	= seconds;
							RTC_TABLE[MINUTES]	= minutes;
							RTC_TABLE[HOURS]	= hours;
							RTC_TABLE[WEEKDAY]	= dayOfWeek(year, month, day);
							RTC_TABLE[DAY]		= day;
							RTC_TABLE[MONTH]	= month;
							RTC_TABLE[YEAR]		= year - 2000;
							ds1302SetTime(RTC_TABLE);
							rprintfProgStr(PSTR("Time set!\r\n"));
						} else {
							error = 1;
							rprintfProgStr(PSTR("Incorrect seconds; correct values: 0-59\r\n"));
						}
					} else {
						error = 1;
						rprintfProgStr(PSTR("Incorrect minutes; correct values: 0-59\r\n"));
					}
				} else {
					error = 1;
					rprintfProgStr(PSTR("Incorrect hours; correct values: 0-23\r\n"));
				}
			} else {
				error = 1;
				rprintfProgStr(PSTR("Incorrect day; correct value based on month and leap year\r\n"));
			}
		} else {
			error = 1;
			rprintfProgStr(PSTR("Incorrect month; correct values: 1-12\r\n"));
		}
	} else {
		error = 1;
		rprintfProgStr(PSTR("Incorrect year; correct values: 2000-2099\r\n"));
	}
	
	if (error > 0) {
		rprintfProgStr(PSTR("Time set failed!\r\n"));
	}
	
	rprintfCRLF();
}

void cliGetTime(void) {
	ds1302GetTime(RTC_TABLE);
	rprintfCRLF();
	rprintfProgStr(PSTR("Current time:\r\n"));
	uint16_t year = RTC_TABLE[YEAR] + RTC_CENTURY * 100;
	rprintfNum(10, 4, FALSE, '0', year);
	rprintfChar('-');
	rprintfNum(10, 2, FALSE, '0', RTC_TABLE[MONTH]);
	rprintfChar('-');
	rprintfNum(10, 2, FALSE, '0', RTC_TABLE[DAY]);
	rprintfChar(' ');
	rprintfNum(10, 2, FALSE, '0', RTC_TABLE[HOURS]);
	rprintfChar(':');
	rprintfNum(10, 2, FALSE, '0', RTC_TABLE[MINUTES]);
	rprintfChar(':');
	rprintfNum(10, 2, FALSE, '0', RTC_TABLE[SECONDS]);
	rprintfCRLF();
}


void drawClock(void) {
	ds1302GetTime(RTC_TABLE);
	rprintfInit(lcdDataWrite);
	lcdGotoXY(6, 0);
	rprintf("%d:%d:%d", RTC_TABLE[HOURS], RTC_TABLE[MINUTES], RTC_TABLE[SECONDS]);
	lcdGotoXY(6, 1);
	rprintf("%d.%d.%d%d", RTC_TABLE[DAY], RTC_TABLE[MONTH], RTC_CENTURY, RTC_TABLE[YEAR]);
	rprintfInit(uartSendByte);
}

// Date calculation functions
// Checks leap years

uint8_t isLeapYear(uint16_t year) {
	if (year % 400 == 0) {
		return(0);
	} else if (year % 100 == 0) {
		return(0);
	} else if (year % 4 == 0) {
		return(1);
	} else {
		return(0);
	}
}

// Calculates weekday for given date using Sakamoto's algorithm

int dayOfWeek(int y, int m, int d)
{
   static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
   y -= m < 3;
   return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}
